package com.allenkerr.web;

import com.allenkerr.client.ApiClient;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent;
import io.vavr.CheckedFunction0;
import io.vavr.control.Try;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ConnectTimeoutException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 熔断器测试接口
 *
 * @author 小柯
 * @date 2019/05/14
 */
@Slf4j
@RestController
@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class CircuitBreakerController {

  @Autowired
  private ApiClient apiClient;

  private final CircuitBreaker circuitBreaker;

  private final static ThreadLocal<String> USERNAME_HOLDER = new NamedThreadLocal<>("username");

  public CircuitBreakerController() {
    //创建一个环状缓冲区大小为 4 的熔断器
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        //触发熔断的失败率阈值，注意是失败率，默认 50%
        .failureRateThreshold(50)
        //熔断器在关闭状态时环状缓冲区的大小
        //如果设定为 4，那么必须有 4 个请求，且失败率满足阈值，才能触发熔断器打开
        .ringBufferSizeInClosedState(4)
        //熔断器在半开状态时环状缓冲区的大小
        .ringBufferSizeInHalfOpenState(2)
        //熔断器从打开状态到半开状态的等待时间
        .waitDurationInOpenState(Duration.ofSeconds(10))
        //配置需要视作失败的异常类型
        .recordExceptions(ConnectTimeoutException.class)
        .build();
    //创建熔断器
    circuitBreaker = CircuitBreaker.of("test", circuitBreakerConfig);
    //监听熔断器事件
    circuitBreaker.getEventPublisher()
        .onStateTransition(event -> {
          logger.warn("==> onStateTransition event: {}.", event);
          CircuitBreakerEvent.Type eventType = event.getEventType();
          logger.warn("==> event type: {}.", eventType);
          CircuitBreaker.StateTransition stateTransition = event.getStateTransition();
          logger.warn("==> event state transition: {}.", stateTransition);
          String username = USERNAME_HOLDER.get();
          logger.warn("==> In this request, username: {}.", username);
        });
  }

  @GetMapping("/one")
  public ResponseEntity<String> one(@RequestParam int value, @RequestParam String username) {

    USERNAME_HOLDER.set(username);

    try {
      //正常方法
      CheckedFunction0<String> normalFunction = CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> {
        //IllegalArgumentException，不视作失败
        if (value == 1) {
          throw new IllegalArgumentException("Illegal argument.");
        }
        //ConnectTimeoutException，视作失败
        if (value == 2) {
          throw new ConnectTimeoutException("Connect timeout.");
        }
        return "normal";
      });
      //降级方法
      Function<Throwable, String> recoveryFunction = throwable -> "recovery";

      Try<String> result = Try.of(normalFunction).recover(recoveryFunction);
      if (result.isFailure()) {
        return ResponseEntity.badRequest().body(result.get());
      }
      return ResponseEntity.ok(result.get());
    } finally {
      USERNAME_HOLDER.remove();
    }
  }

  @GetMapping("/two")
  public ResponseEntity<String> two(@RequestParam long time) {
    try {
      ResponseEntity<String> responseEntity = apiClient.apiThree(time);
      logger.info("{}", responseEntity);
    } catch (Exception e) {
      logger.error("Exception occurred.", e);
    }
    return ResponseEntity.ok("OK");
  }

  @GetMapping("/three")
  public ResponseEntity<String> three(@RequestParam long time) {
    logger.info("CircuitBreakerController.three");
    try {
      TimeUnit.SECONDS.sleep(time);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return ResponseEntity.ok("Hello world.");
  }

}
